/* [n_wb]
 *********************
 * Things to remember:
 *
 * - detail is not threadsafe, it should only be used to .get data, never .set
 * - global variables are global to all threads, use load & unload to initialize & free up memory
 * - use global isInit to initialize global variables requiring detail to be setup
 * - when using global isInit, the code must be mutex protected so all threads do not run the init, just one
 * - inter-thread coordination can be coordinated via the provided mutex
 */
#include <process.h>
#include <stdio.h>

#define DLL_EXPORT
#include "start.h"
#include "Database.h"
#include "PcManager.h"
#include "Exec.h"
#include "Wmi.h"

#include <iostream>
using namespace std;

#ifdef _WIN32
#ifdef _DEBUG
   #include <crtdbg.h>
   #undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
   #define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
   #define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

#ifdef __cplusplus
extern "C" {
#endif	/*__cplusplus*/


// default global variables within dll
ParamManager *supported = NULL;
// module-specific global variables within dll
bool isInit = false;
Database *db = NULL;
PcManager *pcm = NULL;


// called after the module is loaded, useful to load global variables; Returns supported actions as a ParamManager*
DECLDIR ParamManager* load(void) {
	supported = new ParamManager();
	supported->setOverwrite(false);
	supported->set("action", "wb");
	supported->set("action", "wb_add");
	supported->set("action", "wb_del");
	supported->set("action", "wb_set");
	supported->set("action", "wb_sysinfo");
	supported->set("action", "wb_qwinsta");
	supported->set("action", "wb_ping");
	supported->set("action", "wb_li_prod");
	supported->set("action", "wb_analyze");

	return supported;
}


// called just before the module is unloaded, useful to unload global variables
DECLDIR void unload(void) {
	if(supported) {
		delete supported;
	}
	if(db) {
		delete db;
	}
	if(pcm) {
		delete pcm;
	}
}


void init(HANDLE mutex, ParamManager &detail) {
	MYSQL_RES *rUpdate = NULL;

	// ensure we only init once
	if(!isInit) {
		char query[DEFAULT_BUFLEN];

		// Instance Table
		db = new Database(
					detail.get("db_user"), detail.get("db_pass"),
					detail.get("db_name"),
					detail.get("db_host"), atoi(detail.get("db_port")));

/*		// take action db connection failed
		if(&(db->conn) == NULL) {
			return;
		}
*/
		// re-establish mysql connection if lost
		bool connect = true;
		mysql_options(&(db->conn), MYSQL_OPT_RECONNECT, &connect);

		sprintf(query, "CREATE TABLE IF NOT EXISTS n_wb_pc (");
		strcat(query, "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY");
		strcat(query, ", style ENUM('pc', 'user')");
		strcat(query, ", name VARCHAR(256)");
		strcat(query, ", isHidden BOOL");
		strcat(query, ", dt_access DATETIME");
		strcat(query, ")");

		// init is wrapped by mutex, allowing safe mysql call here
		mysql_query(&(db->conn), query);
		rUpdate = mysql_store_result(&(db->conn));
		// free up the result from the mysql query
		mysql_free_result(rUpdate);

		// do something
		isInit = true;
	}
}


DECLDIR void start(HANDLE mutex, ParamManager &detail, char *index) {
	// mutex protected init sequence
	WaitForSingleObject(mutex, INFINITE);	// Request ownership of mutex
	init(mutex, detail);				// Init sequence
	ReleaseMutex(mutex);					// Release mutex for other threads

/*	// take action db connection failed
	if(&(db->conn) == NULL) {
		printf("mysql_real_connect failed\n");
		// free up mysql thread-specific memory
		mysql_thread_end();
		return;
	}
*/

	// Instantiate the PcManager, the thread-safe db interface
	pcm = new PcManager(db, mutex);


	printf("wb: todo = '%s'\r\n", detail.get("todo"));
	if(!strcmp(detail.get("todo"), "wb_add")) {
		if(detail.isSet("netbios")) {
			printf("netbios = '%s'\r\n", detail.get("netbios"));
			pcm->add(detail.get("netbios"));
		}
	}
	else if(!strcmp(detail.get("todo"), "wb_del")) {
		if(detail.isSet("netbios")) {
			printf("netbios = '%s'\r\n", detail.get("netbios"));
			pcm->del(detail.get("netbios"));
		}
	}
	else if(!strcmp(detail.get("todo"), "wb_set")) {
		if(detail.isSet("netbios")) {
			Pc *pc = pcm->get(detail.get("netbios"));
			pc->set(NULL, detail.get("data"), detail.get("value"));
		}
	}
	else if(!strcmp(detail.get("todo"), "wb_sysinfo")) {
		Pc *pc = pcm->get(detail.get("netbios"));
		if(pc) {
			char cmd[DEFAULT_BUFLEN];
			sprintf(cmd, "systeminfo /s %s", detail.get("netbios"));
			char line[DEFAULT_BUFLEN];
			Exec sysinfo(cmd);
			while(fgets(line, DEFAULT_BUFLEN, sysinfo.output)) {
				// skip line if blank
				if(strlen(line) <= 2) {
					printf("skipping line\n");
					continue;
				}

				// Find first non-tab character
				char *data = line;
				while(data[0] == ' ') {
					data ++;
				}
				// locate the ':' character
				char *value = strchr(data, ':');
				if(value) {
					// end data
					value[0] = '\0';
					value ++;

					// clean up value
					while(value[0] == ' ') {
						value ++;
					}

					char *eol = strchr(value, '\r');
					if(eol) {
						eol[0] = '\0';
					}
					else {
						char *eol = strchr(value, '\n');
						eol[0] = '\0';
					}

	//				printf("data is: '%s', value is '%s'\n", data, value);
					// look to see if this is something we want to save
					bool save = false;
					if(!strcmp(data, "Total Physical Memory")) {
						save = true;
					}
					else if(!strcmp(data, "System Boot Time")) {
						save = true;
					}
					else if(!strcmp(data, "OS Name")) {
						save = true;
					}
					else if(!strcmp(data, "System Model")) {
						save = true;
					}
					else if(!strcmp(data, "Original Install Date")) {
						save = true;
					}
					// save the gathered data
					if(save) {
						pc->set(NULL, data, value);
					}
				}
			}
		}
		else {
			printf("Netbios not found\n");
		}
	}
	else if(!strcmp(detail.get("todo"), "wb_qwinsta")) {
		Pc *pc = pcm->get(detail.get("netbios"));
		if(pc) {
			char cmd[DEFAULT_BUFLEN];
			//sprintf(cmd, "cmd /s /c \"C:\\Windows\\System32\\pstools\\psexec.exe \\\\%s qwinsta\"", detail.get("netbios"));
			sprintf(cmd, "C:\\pstools\\psexec.exe \\\\%s c:\\windows\\system32\\qwinsta.exe 2>c:\\temp\\%s_stderr.txt",
					detail.get("netbios"), detail.get("netbios"));
			printf("cmd = '%s'\n", cmd);
			Exec qwinsta(cmd);
			char line[DEFAULT_BUFLEN];
			// ignore the first line
			fgets(line, DEFAULT_BUFLEN, qwinsta.output);
			// start processing lines
			while(fgets(line, DEFAULT_BUFLEN, qwinsta.output)) {
				char *session = line;
				session ++;

				char *username = strchr(session, ' ');
				if(username != NULL) {
					username[0] = '\0';
					username = line + 19;

					char *end = strchr(username, ' ');
					if(end != username) {
						end[0] = '\0';
						printf("session is '%s'\n", session);
						printf("username is '%s'\n", username);
						// Might be better to gather the ID here rather than the session
						// The ID can be used to kill a session, and is maybe more unique
						pc->set("qwinsta", session, username);
					}
	//				else {
	//					printf("not a user: '%s'\n", username);
	//				}
				}
			}
		}
		else {
			printf("Netbios not found\n");
		}
	}
	else if(!strcmp(detail.get("todo"), "wb_ping")) {
		int ping = 0;

		printf("pinging %s\n", detail.get("netbios"));

		Pc *pc = pcm->get(detail.get("netbios"));
		if(pc) {
			// Initialize WMI for use with our ping action
/*			Wmi wmi;
			if(wmi.initialize()) {
				if(wmi.connect()) {
					if(wmi.setSecurity()) {
						// Ping and save the result
						ping = wmi.ping(detail.get("netbios"));
					}
				}
			}
			printf("ping is %d\n", ping);
*/
	char buffer[DEFAULT_BUFLEN];
	char cmd[DEFAULT_BUFLEN];
	sprintf(cmd, "ping -n 1 %s", detail.get("netbios"));
	Exec proc(cmd);
	printf("cmd = %s\n", cmd);

	if(proc.isOutput()) {

		while(fgets(buffer, DEFAULT_BUFLEN, proc.output)) {
			// remove eol
			buffer[strlen(buffer)-1] = 0;

			// exit loop if a blank line is encountered
			if(strlen(buffer) == 0) {
				break;
			}

			printf("buffer: '%s'\n", buffer);

			// string to check for if successful
			if(strstr(buffer, "time=")) {
				ping = 1;
			}
			if(strstr(buffer, "time<")) {
				ping = 1;
			}
		}
	}

			if(ping == 1) {
				pc->set(NULL, "isPing", "1");
			}
			else {
				printf("Ping failed or Wmi Interface not working.\n");
				pc->set(NULL, "isPing", "0");
			}
		}
	}
	else if(!strcmp(detail.get("todo"), "wb_li_prod")) {
		Pc *pc = pcm->get(detail.get("netbios"));
		if(pc) {
			char cmd[DEFAULT_BUFLEN];
			sprintf(cmd, "psexec \\\\%s wmic product get IdentifyingNumber,Name", detail.get("netbios"));
			Exec li_prod(cmd);
			char line[DEFAULT_BUFLEN];
			// ignore the first line
			fgets(line, DEFAULT_BUFLEN, li_prod.output);
			// start processing lines
			while(fgets(line, DEFAULT_BUFLEN, li_prod.output)) {
				// chop off '\r\n'
				char *ptr = strchr(line, '\r');
				if(ptr != NULL) {
					ptr[0] = '\0';
				}
				else {
					char *ptr = strchr(line, '\n');
					ptr[0] = '\0';
				}
				printf("line is '%s'\n", line);

				// Parse out first data 'IdentifyingNumber'
				ptr = strchr(line, '}');
				if(ptr != NULL) {
					ptr ++;
					ptr[0] = '\0';

					char *name = ptr += 2;
				
					// save this list item
					pc->set("li_prod", line, name);	// this should really be improved and save off more information 'wmic product'
				}
			}
		}
		else {
			printf("Netbios not found\n");
		}
	}
	else if(!strcmp(detail.get("todo"), "wb_analyze")) {
		Pc *pc = pcm->get(detail.get("netbios"));
		if(pc) {
			char value[DEFAULT_BUFLEN];
			pc->get("li_prod", "{A92DAB39-4E2C-4304-9AB6-BC44E68B55E2}", value);
			if(value != NULL) {
				pc->set("analyze", "Google Update Helper is installed", "");
			}

	//		printf("get result '%s'\n", value);
		}
		else {
			printf("Netbios not found\n");
		}
	}

	// free up mysql thread-specific memory
	mysql_thread_end();
}


#ifdef __cplusplus
}
#endif	/*__cplusplus */